/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package core.discovery;

import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;

import core.descriptors.TaskDescriptor;
import core.descriptors.WorkerDescriptor;
import core.enums.Errors;
import core.enums.WorkerStatus;
import core.interfaces.Discovery;
import core.util.COREServiceRegistry;
import core.worker.Address;
import java.util.Random;
import org.apache.log4j.Logger;

/**
 * La classe WorkerDiscovery serve ad istanziare il Discovery per ogni peer.
 * Elemento fondamentale della WorkerDiscovery è la lista dei Worker presenti nella rete.
 * La lista e'� una ConcurrentLinkedQueue. La classe implementa l'interfaccia Discovery
 * @author Vincenzo Merlo, Scuderi Giovanni Luca, Paolo Giuffrida
 */

/*
 * TODO: SVILUPPI FUTURI: rendere più intelligente la addworker per il controllo dell'univocità
 */
public class WorkerDiscovery implements Discovery {

    private final Logger logger = COREServiceRegistry.getLogger(WorkerDiscovery.class.toString());
    private Collection<WorkerDescriptor> workers = new ConcurrentLinkedQueue<WorkerDescriptor>();
    private volatile static WorkerDiscovery instance = null;

    /**
     *
     * @return
     */
    public static WorkerDiscovery getInstance() {
        // needed because once there is singleton available no need to acquire
        // monitor again & again as it is costly
        if (instance == null) {
            synchronized (WorkerDiscovery.class) {
                // this is needed if two threads are waiting at the monitor at the
                // time when singleton was getting instantiated
                if (instance == null) {
                    instance = new WorkerDiscovery();
                }
            }
        }
        return instance;
    }

    private WorkerDiscovery() {
        logger.trace(" costructor");
    }

    /**
     * Ritorna la lista dei Workers presenti nella rete
     * @return Collection<WorkerDescriptor> - lista dei workers
     */
    public Collection<WorkerDescriptor> getWorkers() {
        logger.trace("getWorkers");
        return this.workers;
    }

    /**
     * Setta la lista dei workers presenti nella rete
     * @param Collection<WorkerDescriptor> - lista dei workers da settare
     */
    public void setWorkers(Collection<WorkerDescriptor> val) {
        logger.trace("setWorkers");
        this.workers = val;
    }

    /**
     * Ritorna l'indirizzo del worker adatto ad uno specifico TaskDescriptor.
     * In questa prima implementazione questa funzione ritorna il primo worker libero.
     * Quando viene ritornato l'indirizzo del worker a cui mandare il task viene settato il suo stato a "Working".
     * @param td - TaskDescriptor
     * @return Address - indirizzo del worker
     */
    public Address getWorker(TaskDescriptor td) {
        logger.trace("getWorker");
        //io do taskdescript e in base al tipo del worker lui mi torna l'indirizzo ad esso associato!!!!
        //ma workdescritor nn ha nessun riferimento al tipo
        //throw new UnsupportedOperationException("Not supported yet.");

        final Collection<WorkerDescriptor> tmp = workers;
        //cerco nella lista il primo worker disponibile e passo il suo indirizzo!

        synchronized (tmp) {
            Iterator<WorkerDescriptor> workerIt = tmp.iterator();
            while (workerIt.hasNext()) {
                final WorkerDescriptor _worker = ((WorkerDescriptor) workerIt.next());
                if (_worker.getStatus().equals(WorkerStatus.Free)) {
                    _worker.setStatus(WorkerStatus.Working);
                    //TODO: sviluppi futuri deve aggiornare lo stato del worker anche negli altri WorkerDiscovery
                    return _worker.getAddr();
                }
            }
        }

        //Se arrivo qua nn ci sono worker liberi
        //TODO: Lebby: ok ... ma se non ci sono worker liberi allora io li assegno a dei worker che comunque ci sono!
        //TODO: ??? siete d'accordo con la modifica lebbiana ????
        synchronized (tmp) { //aggiunto randomizzatore ... aggiungiamo un po di pepe alla scansione stupida della lista
            Random rand = new Random();
            int pos = rand.nextInt(this.workers.size()-1);
            Iterator<WorkerDescriptor> workerIt = tmp.iterator();
            int i = 0 ;
            while (workerIt.hasNext()) {

                final WorkerDescriptor _worker = ((WorkerDescriptor) workerIt.next());
                if ( i == pos)
                return _worker.getAddr();
                i ++ ;
            }
        }
        //ORA arrivo qua se non ci sono Worker ... e qui non ci posso fare niente!


        return null;

    }

    /**
     * Inserisce un Worker nella lista
     * @param WorkerDescriptor
     * @return int > 0 indice della lista in cui è stato inserito il worker
     *         int < 0 errore
     */
    public int addWorker(WorkerDescriptor wd) {

        final Collection<WorkerDescriptor> tmp = workers;
        logger.trace("added worker" + wd.getAddr().getIP());
        synchronized (tmp) {
            if (tmp.add(wd)) {

                return (tmp.size() - 1);
            } else {
                return Errors.Otherwise.code();
            }
        }
    }

    /**
     * Cambia il valore dello stato del worker (da "Free" a "Working" e viceversa)
     * In questa prima implementazione noi useremo questa funzione solamente per settare lo stato a "Free"
     * @return int > 0 - cambiamento effettuato
     *         int < 0 errore
     */
    public int updateWorker(int index, WorkerDescriptor wd) {
        logger.trace("updateWorker");
        final Collection<WorkerDescriptor> tmp = workers;
        synchronized (tmp) {
            Iterator<WorkerDescriptor> workerIt = tmp.iterator();
            while (workerIt.hasNext()) {
                final WorkerDescriptor _worker = ((WorkerDescriptor) workerIt.next());
                if (_worker.getAddr().equals(wd.getAddr())) {
                    if (_worker.getStatus().equals(WorkerStatus.Free)) {
                        _worker.setStatus(WorkerStatus.Working);
                        return 1;
                    } else {
                        _worker.setStatus(WorkerStatus.Free);
                        return 1;
                    }
                }
            }
            return Errors.Otherwise.code();
        }
    }

    /**
     *
     * @return string
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(WorkerDiscovery.class.getSimpleName());
        for (WorkerDescriptor wd : this.getWorkers()) {
            sb.append("\n\t").append(wd);
        }

        return sb.toString();

    }
}
